# Writing a program for BabyOS

Programs run in **ring 3** (user mode). They cannot touch hardware, cannot
disable interrupts, and cannot read kernel memory. The only way to affect
anything outside your own address space is a **syscall**: `int $0x80`.

There is no standard library. No `printf`, no `malloc`, no `string.h`, no
`main`. Everything you use, you write.

---

## The syscall interface

Arguments go in registers. `eax` selects the call, `ebx` onward carry
arguments, and the return value comes back in `eax`.

| eax | name  | arguments        | returns |
|-----|-------|------------------|---------|
| 1   | write | `ebx` = `char *` | 0       |
| 2   | exit  | —                | does not return |

`write` takes a null-terminated string and prints it to the screen.
`exit` terminates the program and returns control to the shell.

Wrappers, which you can copy verbatim:

```c
static int sys_write(const char *s) {
    int ret;
    __asm__ volatile ("int $0x80" : "=a"(ret) : "a"(1), "b"(s));
    return ret;
}

static void sys_exit(void) {
    __asm__ volatile ("int $0x80" : : "a"(2));
}
```

The `"a"` and `"b"` constraints pin those values to `eax` and `ebx`, which
is what the kernel's handler reads.

---

## Rules

**Entry point is `_start`, not `main`.** There is no C runtime to set up
argv, initialise globals, and call `main` for you. Execution begins at
`_start` directly.

**Always call `sys_exit()`.** If you fall off the end of `_start`, the CPU
carries on executing whatever bytes follow — usually garbage. Put an
infinite loop after the exit call as a safety net.

**Link at `0x40000000`.** Absolute addresses are baked into the binary at
link time, so the link address and the load address must match. Use the
provided `user.ld`.

**Output a flat binary.** The loader copies raw bytes to memory; it does
not parse ELF headers. `objcopy -O binary` strips everything but the
image.

**Names are truncated to 8 characters** and lose the `.bin` extension.
`mycalculator.bin` becomes `mycalcul` on disk.

**One program at a time.** Every program is linked at the same address and
they share a single address space. Starting a second while the first is
running will corrupt both.

---

## A minimal program

`myprog.c`:

```c
static int sys_write(const char *s) {
    int ret;
    __asm__ volatile ("int $0x80" : "=a"(ret) : "a"(1), "b"(s));
    return ret;
}

static void sys_exit(void) {
    __asm__ volatile ("int $0x80" : : "a"(2));
}

void _start(void) {
    sys_write("hello from myprog\n");
    sys_exit();
    for (;;) {}
}
```

---

## Building it

Three rules in the Makefile — copy the `hello` ones and change the name:

```make
myprog.o: myprog.c
	clang --target=i386-elf -ffreestanding -fno-stack-protector \
	      -fno-pic -Wall -Wextra -c myprog.c -o myprog.o

myprog.elf: myprog.o user.ld
	ld.lld -m elf_i386 -T user.ld -o myprog.elf myprog.o

myprog.bin: myprog.elf
	objcopy -O binary myprog.elf myprog.bin
```

Then add it to the disk image:

```make
disk.img: hello.bin myprog.bin
	python3 mkdisk.py hello.bin myprog.bin
```

---

## Running it

```
make run
```

At the prompt:

```
> run myprog
hello from myprog
>
```

---

## Disk layout

For reference, in case you want to build images another way.

Sector 0 is a directory of up to 32 entries, 16 bytes each:

| offset | size | field                          |
|--------|------|--------------------------------|
| 0      | 8    | name, null-padded              |
| 8      | 4    | starting LBA (little-endian)   |
| 12     | 4    | length in bytes (little-endian)|

An entry whose first byte is zero marks the end of the directory.

Program data starts at sector 1. Each program begins on a sector boundary
and is padded with zeros to a multiple of 512 bytes.

---

## Known limitations

These are real and will bite if you push on them:

- Programs share one address space; only one can run at a time.
- Memory is not reclaimed when a program exits — each run leaks pages.
- The kernel does not validate pointers passed to syscalls. A bad pointer
  passed to `write` will be dereferenced in ring 0.
- Kernel stacks have no guard page; deep recursion silently corrupts
  neighbouring memory.
- `tss.esp0` is fixed at the boot stack rather than per-task.
